import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter_app_demo/base/environment.dart';
import 'package:flutter_app_demo/helper/account_helper.dart';
import 'package:flutter_app_demo/helper/app_helper.dart';
import 'package:flutter_app_demo/helper/event_bus_helper.dart';
import 'package:flutter_app_demo/helper/hive_helper.dart';
import 'package:flutter_app_demo/helper/user_agent_helper.dart';
import 'package:flutter_app_demo/net/api_exception.dart';
import 'package:flutter_app_demo/net/base_data.dart';
import 'package:flutter_app_demo/net/common_api.dart';
import 'package:flutter_app_demo/utils/aes_utils.dart';
import 'package:flutter_app_demo/utils/logger.dart';

/// Http 请求代理类
///
/// Created by wangliang on 2024/7/31

enum HttpMethod { get, post, put }

class HttpAgent {
  static const int defaultServerErrorCode = -900;
  static const String defaultErrorTips = "网络请求失败，请稍后重试";

  static const String aesReqKey = '4ca7dacc9332d74e1292c83f0aa3b376';

  static const bool needEncrypt = false;

  static const int tokenInvalidCode = 305208;
  static const int tokenInvalidKickOut = 305238;

  HttpAgent._();

  static final HttpAgent instance = HttpAgent._();

  Future<T?> doGetRequest<T>(String url, Map<String, dynamic>? params, {String? baseUrl}) async {
    dynamic data = await doRequest(url, params, null, HttpMethod.get, baseUrl: baseUrl);
    if (data is Map<String, dynamic>) {
      return ModelConvertUtils.fromJson<T>(data);
    } else {
      return data;
    }
  }

  Future<T?> doPostRequest<T>(String url, Map<String, dynamic> params, {String? baseUrl}) async {
    dynamic data = await doRequest(url, null, params, HttpMethod.post, baseUrl: baseUrl);
    if (data is Map<String, dynamic>) {
      return ModelConvertUtils.fromJson<T>(data);
    } else {
      return data;
    }
  }

  Future<T?> doPutRequest<T>(String url, Map<String, dynamic> params, {String? baseUrl}) async {
    dynamic data = await doRequest(url, null, params, HttpMethod.put, baseUrl: baseUrl);
    if (data is Map<String, dynamic>) {
      return ModelConvertUtils.fromJson<T>(data);
    } else {
      return data;
    }
  }

  Future<List<T>?> doGetRequestResponseList<T>(String url, Map<String, dynamic>? params, {String? baseUrl}) async {
    dynamic data = await doRequest(url, params, null, HttpMethod.get, baseUrl: baseUrl);
    List<dynamic>? respData = data as List<dynamic>?;
    if (respData == null) {
      return null;
    }
    List<T> list = [];
    for (var item in respData) {
      if (item is Map<String, dynamic>) {
        list.add(ModelConvertUtils.fromJson<T>(item));
      } else {
        list.add(item);
      }
    }
    return list;
  }

  static Future<Map<String, String>> getDefaultHeaders({String url = ''}) async {
    Map<String, String> headerParams = {};
    String userAgent = await UserAgentHelper.instance.getWebViewUserAgent();
    String accessToken = AccountHelper.instance.getAccessToken() ?? "";
    if (url != CommonApi.refreshTokenUrl) {
      headerParams['accesstoken'] = accessToken;
    }
    headerParams['user-agent'] = userAgent;

    String deviceCode = await AppHelper.instance.getDeviceCode();
    headerParams['platformid'] = AppHelper.instance.platformId;
    headerParams['devicecode'] = deviceCode;
    headerParams['version'] = await AppHelper.instance.appVersion;
    return headerParams;
  }

  Future<dynamic> doRequest(
      String url, Map<String, dynamic>? queryParams, Map<String, dynamic>? data, HttpMethod method,
      {String? baseUrl}) async {
    return await _doRealRequest(url, queryParams, data, method, baseUrl: baseUrl);
  }

  Future<dynamic> _doRealRequest(
    String url,
    Map<String, dynamic>? queryParams,
    Map<String, dynamic>? data,
    HttpMethod httpMethod, {
    String? baseUrl,
    bool retry = false,
  }) async {
    String method = 'GET';
    switch (httpMethod) {
      case HttpMethod.get:
        method = 'GET';
        break;
      case HttpMethod.post:
        method = 'POST';
        break;
      case HttpMethod.put:
        method = 'PUT';
        break;
    }

    // header params
    Map<String, dynamic> headerParams = await getDefaultHeaders(url: url);
    String deviceCode = headerParams['ruixue-devicecode'] ?? '';

    Object? postData;
    if (needEncrypt && !retry) {
      try {
        headerParams['ruixue-encipher'] = '1';
        headerParams['Content-Type'] = 'text/plain';
        if (httpMethod != HttpMethod.get && data != null) {
          postData = AESUtils.encryptCBCPKCS7(AESUtils.calculateMd5(deviceCode + aesReqKey), jsonEncode(data));
        } else {
          postData = data;
        }
      } catch (e) {
        Log.d('post data encrypt failed $e');
        headerParams['ruixue-encipher'] = '0';
        headerParams['Content-Type'] = 'application/json';
        postData = data;
      }
    } else {
      headerParams['ruixue-encipher'] = '0';
      headerParams['Content-Type'] = 'application/json';
      postData = data;
    }
    BaseOptions options = BaseOptions(
        baseUrl: baseUrl ?? Environment.baseUrl,
        method: method,
        connectTimeout: const Duration(milliseconds: 12000),
        sendTimeout: const Duration(milliseconds: 12000),
        receiveTimeout: const Duration(milliseconds: 12000),
        headers: headerParams);
    var dio = Dio(options);

    await _setProxy(dio);

    try {
      Response response = await dio.request(url, queryParameters: queryParams, data: postData);
      if (response.statusCode != HttpStatus.ok) {
        int code = response.statusCode ?? defaultServerErrorCode;
        String message = response.statusMessage ?? defaultErrorTips;
        throw ApiException(code: code, message: message);
      }

      int code = response.data['code'] as int;
      String message = response.data['msg'] as String? ?? defaultErrorTips;

      if (code == tokenInvalidKickOut) {
        // KickOutHelper.instance.showKickOutDialog(message);
        await AccountHelper.instance.clear();
        // message 无需传值，避免弹出 toast
        throw ApiException(code: code, message: '');
      } else if (code == tokenInvalidCode) {
        if (url.contains('v1/gamer-club/user/token/refresh')) {
          // 直接抛出异常，直接走到下边 token 为 空的逻辑
          throw ApiException(code: code, message: message);
        }
        String? token = await AccountHelper.instance.refreshToken();
        AccountHelper.instance.clearRefreshFuture();
        if (token == null) {
          Log.d('refresh token 失败, 说明 refresh 也过期了');
          // 刷新后接口异常，返回空，此种情况认为是 logout
          EventBusHelper.instance.defaultEventBus.fire(LogoutEvent());
          throw ApiException(code: defaultServerErrorCode, message: defaultErrorTips);
        } else {
          // 重试一次
          return await _doRealRequest(url, queryParams, data, httpMethod, baseUrl: baseUrl);
        }
      } else if (code == 0) {
        String encipher = response.headers.value('ruixue-encipher') ?? '';
        if ('1' == encipher) {
          String dataStr = response.data['data'] as String? ?? '';
          if (dataStr.isNotEmpty) {
            try {
              String decryptStr = AESUtils.decryptCBCPKCS7(AESUtils.calculateMd5(deviceCode + aesReqKey), dataStr);
              if (decryptStr.startsWith('{')) {
                return jsonDecode(decryptStr);
              } else if (decryptStr.startsWith('[')) {
                return json.decode(decryptStr);
              } else {
                return decryptStr;
              }
            } catch (e) {
              if (!retry) {
                return await _doRealRequest(url, queryParams, data, httpMethod, baseUrl: baseUrl, retry: true);
              } else {
                Log.d('解密失败，并且重试未加密仍然失败');
                throw ApiException(code: code, message: message);
              }
            }
          } else {
            return response.data['data'];
          }
        } else {
          return response.data['data'];
        }
      } else if (code == 302015 || code == 302016) {
        if (!retry) {
          return await _doRealRequest(url, queryParams, data, httpMethod, baseUrl: baseUrl, retry: true);
        } else {
          throw ApiException(code: code, message: message);
        }
      } else {
        throw ApiException(code: code, message: message);
      }
    } on DioException catch (exception) {
      throw _obtainDioException(exception);
    } on ApiException {
      rethrow;
    } catch (ex) {
      Log.d('$url except dio exception $ex ');
      throw ApiException(code: defaultServerErrorCode, message: defaultErrorTips);
    }
  }

  Future<dynamic> doPostTrackResponse(String url, Map<String, dynamic> headerParams, String data,
      {bool retry = false}) async {
    String method = 'POST';

    String deviceCode = headerParams['ruixue-devicecode'];

    Object? postData;
    if (needEncrypt && !retry) {
      try {
        headerParams['ruixue-encipher'] = '1';
        headerParams['Content-Type'] = 'text/plain';
        postData = AESUtils.encryptBytesCBCPKCS7(
            AESUtils.calculateMd5(deviceCode + aesReqKey), GZipCodec().encode(utf8.encode(data)));
      } catch (e) {
        Log.d('post data encrypt failed $e');
        headerParams['ruixue-encipher'] = '0';
        headerParams['Content-Type'] = 'application/json';
        postData = GZipCodec().encode(utf8.encode(data));
      }
    } else {
      headerParams['ruixue-encipher'] = '0';
      headerParams['Content-Type'] = 'application/json';
      postData = GZipCodec().encode(utf8.encode(data));
    }

    BaseOptions options = BaseOptions(
        baseUrl: Environment.baseUrl,
        method: method,
        connectTimeout: const Duration(milliseconds: 12000),
        sendTimeout: const Duration(milliseconds: 12000),
        receiveTimeout: const Duration(milliseconds: 12000),
        headers: headerParams);
    var dio = Dio(options);
    dio.transformer = BackgroundTransformer();
    await _setProxy(dio);
    try {
      Response response = await dio.request(url, data: postData);
      if (response.statusCode != HttpStatus.ok) {
        int code = response.statusCode ?? defaultServerErrorCode;
        String message = response.statusMessage ?? defaultErrorTips;
        throw ApiException(code: code, message: message);
      }
      int code = response.data['code'] as int;
      String message = response.data['msg'] as String? ?? defaultErrorTips;
      if (code == 0) {
        String encipher = response.headers.value('ruixue-encipher') ?? '';
        if ('1' == encipher) {
          String dataStr = response.data['data'] as String? ?? '';
          if (dataStr.isNotEmpty) {
            try {
              String decryptStr = AESUtils.decryptCBCPKCS7(AESUtils.calculateMd5(deviceCode + aesReqKey), dataStr);
              if (decryptStr.startsWith('{')) {
                return jsonDecode(decryptStr);
              } else if (decryptStr.startsWith('[')) {
                return json.decode(decryptStr);
              } else {
                return decryptStr;
              }
            } catch (e) {
              if (!retry) {
                return await doPostTrackResponse(url, headerParams, data, retry: true);
              } else {
                Log.d('解密失败，并且重试未加密仍然失败');
                throw ApiException(code: code, message: message);
              }
            }
          } else {
            return response.data['data'];
          }
        } else {
          return response.data['data'];
        }
      } else if (code == 302015 || code == 302016) {
        // 重试一次
        if (!retry) {
          return await doPostTrackResponse(url, headerParams, data, retry: true);
        } else {
          throw ApiException(code: code, message: message);
        }
      } else {
        throw ApiException(code: code, message: message);
      }
    } on DioException catch (exception) {
      throw _obtainDioException(exception);
    } on ApiException {
      rethrow;
    } catch (ex) {
      throw ApiException(code: defaultServerErrorCode, message: defaultErrorTips);
    }
  }

  Future _setProxy(Dio dio) async {
    if (!Environment.isTest) {
      return;
    }
    bool useProxy = ProxyHiveHelper.instance.proxyEnabled ?? false;
    String proxyHost = ProxyHiveHelper.instance.proxyIp ?? '';
    // Log.d('use proxy $proxyHost proxy enable $useProxy');
    if (!useProxy || proxyHost.isEmpty) {
      return;
    }
    if (useProxy) {
      // Charles 抓包配置 开发者可以用，提交时注释掉，HACK 的方法
      (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
        client.findProxy = (uri) {
          return "PROXY $proxyHost:8888";
        };
        client.badCertificateCallback = (cert, host, post) {
          return true;
        };
        return null;
      };
    }
  }

  ApiException _obtainDioException(DioException dioError) {
    if (dioError.type == DioExceptionType.badResponse) {
      if (dioError.response != null) {
        Map<String, dynamic>? data = dioError.response?.data;
        Log.d('dio error response code : ${data?['code']} $dioError');
        int code = data?['code'] ?? defaultServerErrorCode;
        String message = data?['message'] ?? defaultErrorTips;
        return ApiException(code: code, message: message);
      } else {
        Log.i('dio error response null');
        return ApiException(code: defaultServerErrorCode, message: defaultErrorTips);
      }
    } else {
      Log.i('dio error $dioError');
      return ApiException(code: defaultServerErrorCode, message: defaultErrorTips);
    }
  }
}
